<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Minio测试分片上传</title>
    <script src="js/vue.js"></script>
    <script src="js/jquery.js"></script>
    <script src="js/spark-md5.min.js"></script>
</head>
<body>
<div id="app">
    <form @submit.prevent="uploadFile">
        <label>
            文件
            <input type="file" @change="fileChange">
        </label>
        <br>
        <br>
        <button type="submit" v-show="file != null">上传文件</button>
    </form>
    <p v-if="uploadProgress">{{ uploadProgress }}%</p>
</div>

<script>
    var vm = new Vue({
        el: "#app",
        data() {
            return {
                file: null,
                chunkSize: 5 * 1024 * 1024, // 5MB 每块
                uploadId: null,
                etags: [],
                uploadProgress: 0
            };
        },
        methods: {
            /**
             * 选择文件
             */
            fileChange(event) {
                this.file = event.target.files[0];
                console.log(this.file)
            },
            /**
             * 分片上传文件
             */
            async uploadFile() {
                if (!this.file) {
                    alert('请选择一个文件');
                    return;
                }

                // 清空数据
                this.uploadId = null
                this.etags = []
                this.uploadProgress = 0

                const fileSize = this.file.size;
                const isLt2M = fileSize / 1024 / 1024 < 50;

                if (!isLt2M) {
                    alert("上传文件大小不能超过 50MB!");
                    return;
                }

                if (fileSize <= this.chunkSize) {
                    await this.uploadSingleFile();
                } else {
                    await this.uploadChunkedFile();
                }
            },
            /**
             * 单文件上传
             */
            async uploadSingleFile() {
                try {
                    const url = await this.getPresignedUrl(this.file.name, this.file.type);
                    await this.putObject(url, this.file);
                    alert("文件上传成功");
                } catch (error) {
                    alert(`上传失败: ${error.message}`);
                }
            },
            /**
             * 分片上传
             */
            async uploadChunkedFile() {
                try {
                    const resp = await this.initializeMultipartUpload(this.file.name, this.file.type);
                    this.uploadId = resp.uploadId;
                    const parts = resp.parts;

                    // 创建所有分片上传的Promise数组
                    const uploadPromises = parts.map(async (part) => {
                        console.log(part)
                        const startSize = (part.parkNum - 1) * this.chunkSize;
                        const endSize = part.parkNum === parts.length ? this.file.size : startSize + this.chunkSize;
                        const chunkFile = this.file.slice(startSize, endSize);
                        const result = await this.uploadFilePart(part.uploadUrl, chunkFile, part.parkNum);
                        this.etags.push(result);
                        this.updateProgress(part.parkNum, parts.length);
                    });

                    // 并发执行所有分片上传
                    await Promise.all(uploadPromises);

                    // 完成分片上传
                    await this.completeMultipartUpload(this.file.name);
                    alert("文件上传成功");
                } catch (error) {
                    alert(`上传失败: ${error.message}`);
                }
            },
            /**
             * 初始化多部分上传
             */
            initializeMultipartUpload(fileName, contentType) {
                return new Promise((resolve, reject) => {
                    $.get(`http://localhost:9989/oss/multipart/url?chunkNum=${Math.ceil(this.file.size / this.chunkSize)}&fileName=${fileName}&contentType=${contentType}`, (resp) => {
                        resolve(resp);
                    }).fail(() => reject(new Error("获取分片数据失败")));
                });
            },
            /**
             * 获取预签名URL
             */
            getPresignedUrl(fileName, contentType) {
                return new Promise((resolve, reject) => {
                    $.get(`http://localhost:9989/oss/presign/url?bucketName=demo&fileName=${fileName}&contentType=${contentType}`, (url) => {
                        resolve(url);
                    }).fail(() => reject(new Error("无法获取预签名URL")));
                });
            },
            /**
             * 上传单个分片
             */
            uploadFilePart(uploadUrl, chunkFile, partNumber) {
                return new Promise((resolve, reject) => {
                    $.ajax({
                        type: "PUT",
                        url: uploadUrl,
                        data: chunkFile,
                        cache: false,
                        processData: false,
                        contentType: false,
                        success: function (response, textStatus, jqXHR) {
                            const tag = jqXHR.getResponseHeader('ETag');
                            resolve({partNumber, tag});
                        },
                        error: (xhr, status, error) => reject(new Error(`分片${partNumber}上传失败: ${error}`))
                    });
                });
            },
            /**
             * 完成多部分上传
             */
            completeMultipartUpload(fileName) {
                return new Promise((resolve, reject) => {
                    $.ajax({
                        url: "http://localhost:9989/oss/multipart/complete",
                        type: "POST",
                        contentType: "application/json", // 明确设置内容类型为JSON
                        data: JSON.stringify({
                            uploadId: this.uploadId,
                            etags: this.etags,
                            fileName: fileName
                        }),
                        success: (response) => {
                            resolve(response);
                        },
                        error: (xhr, status, error) => {
                            reject(new Error(`完成分片上传失败: ${error}`));
                        }
                    });
                });
            },
            /**
             * 更新上传进度
             */
            updateProgress(currentPart, totalParts) {
                this.uploadProgress = Math.round((currentPart / totalParts) * 100);
            },
            /**
             * 计算文件MD5（可选）
             */
            getFileMd5(file) {
                return new Promise((resolve, reject) => {
                    const spark = new SparkMD5.ArrayBuffer();
                    const reader = new FileReader();

                    reader.onload = (e) => {
                        spark.append(e.target.result);
                        resolve(spark.end());
                    };

                    reader.onerror = () => {
                        reject(new Error('无法读取文件'));
                    };

                    reader.readAsArrayBuffer(file);
                });
            }
        }
    });
</script>
</body>
</html>